Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方式) 您所在的位置:网站首页 unity 画线后在垂直方向生成点 Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方式)

Unity: 一个简单的镜头移动/缩放管理(镜头固定,移动地图方式)

2023-04-25 19:43| 来源: 网络整理| 查看: 265

使用拖动地图,镜头固定的方式,能对touch点进行精确的拖动,不受透视影响. 边界控制(可视范围控制)是通过判断FOV的4个点(也是屏幕的4个角)与地图的hit交点是否在指定的范围内来做的. 缩放是通过移动镜头高度来做的. 提供定点缩放, 缩放时如果超出可视范围,会对缩放点进行移动. 需要在Hierarchy中添加EasyTouch. 如果要想使用镜头移动方式,具体参考: Unity: 一个简单的镜头移动/缩放管理类(只移动镜头方式)

代码如下:

using UnityEngine; using HedgehogTeam.EasyTouch; using XLua; using System.Collections.Generic; #if UNITY_EDITOR using UnityEditor; #endif namespace DCG { /// /// 摄像机管理类: 固定摄像机,移动地图的方式. /// 挂载到摄像机所在的GameObject /// @Author: Danny Yan /// [RequireComponent(typeof(Camera))] [LuaCallCSharp] public class CameraViewFixedCam : MonoBehaviour { [Header("moveTarget可视边界(localPos):左上->右上->右下->左下")] /// 为了方便配置,设计为相对于moveTarget的localPosition,而不是全局坐标 public Vector3[] moveTargetLocalBounds = new Vector3[4]{ new Vector3(100f, 0f, 900f), new Vector3(900f, 0f, 900f), new Vector3(900f, 0f, 100f), new Vector3(100f, 0f, 100f) }; [Header("缩放时的最高高度")] public float scaleMaxY = 160; [Header("缩放时的最低高度")] public float scaleMinY = 100; [Header("缩放速度,值越大缩放越快")] public float scaleSpeed = 35f; [Header("moveTarget移动的目标点(缓动方式)")] public Vector3 lerpMoveTarget = Vector3.zero; [Header("手势滑动结束后,需要继续移动的系数,值越大移动得越远")] public float lerpGoOnMoveScale = 1.5f; [Header("手势滑动结束后,继续(减速)移动的速度,值越大移动得越快")] public float lerpMoveSpeed = 10f; public float camDistance { get { var _camDistance = 0f; Ray ray = new Ray(transform.position, transform.forward); RaycastHit[] hits = Physics.RaycastAll(ray, this.rayMaxDistance); for (int i = 0; i < hits.Length; i++) { if (hits[i].collider.gameObject == this.moveTarget) { _camDistance = hits[i].distance; break; } } this._scopeDitry = true; this.GetScreenCornersPosInWorld(); return _camDistance; // return this._camDistance; } } [HideInInspector] public int moveToBorderStatus = 0; [Header("摄像机最大射线距离")] public int rayMaxDistance = 5000; [Header("需要移动的目标对象(必须设置)")] public GameObject moveTarget; private Vector4 currentScope = Vector4.zero; [SerializeField] [Header("摄像机中心射线和场景的距离")] // private float _camDistance = float.NaN; private Vector3[] _camCornersCache = new Vector3[4]; private bool _scopeDitry = true; private bool swipeTargetValid = false; /// swipe时记录上一次的世界坐标 private Vector3 preSwipeWorldPos; /// 滑动期间的最后几次变化 private Vector3[] swipeIncrement = new Vector3[5]; private int swipeIncrementIndex = 0; internal Camera mainCamera; /// 是否要进行lerpMove internal bool lerpMove = false; [SerializeField] internal bool showDebugLines = false; // 是否正在缩放 private bool isPinching = false; // 是否锁定并阻止swipe private bool lockTouch = false; private Vector3[] ScreenCorners; private Vector3[] screenViewTrapezoid = new Vector3[4]; private void Awake() { ScreenCorners = new Vector3[]{ new Vector2(0,Screen.height), new Vector2(Screen.width,Screen.height), new Vector2(Screen.width,0), new Vector2(0,0), }; this.mainCamera = this.gameObject.GetComponent(); this.lerpMove = false; if (this.moveTarget == null) { throw new UnityException("必须指定一个移动目标"); } lerpMoveTarget = this.moveTarget.transform.position; this.GetScreenCornersPosInWorld(); EasyTouch.On_Pinch += EasyTouch_On_Pinch; EasyTouch.On_SwipeStart += EasyTouch_On_SwipeStart; EasyTouch.On_Swipe += EasyTouch_On_Swipe; EasyTouch.On_SwipeEnd += EasyTouch_On_SwipeEnd; EasyTouch.On_TouchDown2Fingers += EasyTouch_On_TouchDown2Fingers; EasyTouch.On_TouchUp2Fingers += EasyTouch_On_TouchUp2Fingers; } public void LookAt(GameObject tag, bool useLerp = true) { this.LookAt(tag.transform.position, useLerp); } ///移动到目标位置,并使其与摄像机中心位置对齐 public void LookAt(Vector3 worldPos, bool useLerp = true) { this.CancelLerpMove(); // 1.使用射线检测moveTarget的BoxCollider来得到hitPoint(世界坐标), // 2.使用该hitPoint与worldPos的向量差得到偏移量, // 所以需要设置boxCollider的size.y,使其和worldPos.y一致, // 这样才使得worldPos的y平面位于屏幕中心 var bc = this.moveTarget.GetComponent(); var size = bc.size; size.y = (worldPos.y - this.moveTarget.transform.position.y) * 2; bc.size = size; // 获取相机中心射线检测到的位置 Ray ray = new Ray(this.transform.position, transform.forward); var hits = Physics.RaycastAll(ray, this.rayMaxDistance); var camRayPoint = worldPos; if (hits != null) { for (int i = 0; i < hits.Length; i++) { if (hits[i].collider.gameObject == this.moveTarget) camRayPoint = hits[i].point; } } var transOff = camRayPoint - worldPos; transOff.y = 0; transOff = this.WrapPosInRect(transOff, true); this.MoveTo(this.moveTarget.transform.position+transOff, useLerp); } /// 移动到相对于moveTarget的位置 public void LookAtLocal(Vector3 tagLocalPos, bool useLerp = true) { var tagPos = this.moveTarget.transform.TransformPoint(tagLocalPos); this.LookAt(tagPos, useLerp); } public void MoveTo(Vector3 tagPos, bool useLerp = true) { // this._scopeDitry = true; this.lerpMove = useLerp; if (useLerp) { this.lerpMoveTarget = tagPos; } else { this.moveTarget.transform.position = tagPos; } } public void Scale(float scaleDelta, Vector2 scaleCenterPosOnScreen) { // 计算摄像机视口(摄像机显示画面)的宽高 float halfFOV = (this.mainCamera.fieldOfView * 0.5f) * Mathf.Deg2Rad; float aspect = this.mainCamera.aspect; // 视口在Z轴上变化时(相当于缩放效果),对应的宽高变化量,相当于直接使用scaleDelta作为Z轴的变化距离 // 乘2是因为scaleDelta * Mathf.Tan(halfFOV)计算出的只是视口画面的一半高,缩放是全画面,需要乘2 float height = scaleDelta * Mathf.Tan(halfFOV) * 2; float width = height * aspect; // 缩放中心点在屏幕中的比例,减0.5f,因为世界坐标是相对于屏幕的中心 float cpRateX = scaleCenterPosOnScreen.x / Screen.width - 0.5f; float cpRateY = scaleCenterPosOnScreen.y / Screen.height - 0.5f; Vector3 pos = this.transform.position; var oldPos = this.transform.position; // scaleW*cpRateX 表示视口画面宽度变化偏移度. // 如果cpRateX,cpRateY都为0,表示X轴,Y轴上无变化,则只以transform.forward为实际变化,效果为沿着视口中心的路径上(Z轴)前进/后退. // 比如cpRateX为0.2f,表示在屏幕中心右侧20%位置处作为手势缩放中心点进行操作, // scaleW此时假如为-5(表示放大),则transform.right就还需要往左走-1f, // 最终效果为transform.forward按scaleDelta前进,同时X轴往左移动,这样视觉上20%位置处没有发生任何偏移. Y轴同理 var w = width * cpRateX; var h = height * cpRateY; pos += transform.right * w; pos += transform.up * h; pos += transform.forward * scaleDelta; if (pos.y = scaleMinY) { // 进行一次预缩放 this.transform.position = pos; this._scopeDitry = true; this.GetScreenCornersPosInWorld(); // 缩放后对边界的检查,如果触碰到边界则需要对moveTarget进行transOff偏移, var screenViewTrapezoid = this.GetScreenCornersPosInWorld(); var moveTargetPos = this.moveTarget.transform.position; var st = this.CheckScreenCornerIsOutBounds(moveTargetPos); var transOff = Vector3.zero; transOff = this.WrapTransOff(moveTargetPos, st, transOff); this.moveTarget.transform.Translate(transOff, Space.World); } } /// 获取可视区域(屏幕四个角)对应的世界坐标. /// 可用于在小地图上显示视口的梯形范围 public Vector3[] GetScreenCornersPosInWorld() { if (this._scopeDitry == false) return this.screenViewTrapezoid; this._scopeDitry = false; for (int i = 0; i < ScreenCorners.Length; i++) { Ray ray = this.mainCamera.ScreenPointToRay(ScreenCorners[i]); var hits = Physics.RaycastAll(ray, this.rayMaxDistance); Vector3 dist0 = Vector2.zero; dist0.z = float.MaxValue; if (hits != null && hits.Length > 0) { for (int j = 0; j < hits.Length; j++) { if (hits[j].collider.gameObject == this.moveTarget) { this.screenViewTrapezoid[i] = hits[j].point; break; } } } else { // 没有检测到 if (i == 0) { this.screenViewTrapezoid[i].x -= 1; this.screenViewTrapezoid[i].z += 1; } else if (i == 1) { this.screenViewTrapezoid[i].x += 1; this.screenViewTrapezoid[i].z += 1; } else if (i == 2) { this.screenViewTrapezoid[i].x += 1; this.screenViewTrapezoid[i].z -= 1; } else if (i == 3) { this.screenViewTrapezoid[i].x -= 1; this.screenViewTrapezoid[i].z -= 1; } } } return screenViewTrapezoid; } /// 取消移动 public void CancelLerpMove() { this.lerpMove = false; this.lerpMoveTarget = Vector3.zero; } // ----------------------------------------------------- /// 移动moveTarget private void MoveWithScreenPos(Vector2 screenPosition) { this.CancelLerpMove(); Vector3 transOff = new Vector3(0, 0, 0); var _ray = this.mainCamera.ScreenPointToRay(screenPosition); var _hits = Physics.RaycastAll(_ray, this.rayMaxDistance); for (int i = 0; i < (_hits == null ? 0 : _hits.Length); i++) { if (_hits[i].collider.gameObject == this.moveTarget) { transOff = _hits[i].point - this.preSwipeWorldPos; // 当前touch的世界坐标和上一次记录的世界坐标的方向向量 this.preSwipeWorldPos = _hits[i].point; } } transOff.y = 0; if (transOff == Vector3.zero) { return; } transOff = this.WrapPosInRect(transOff); this.moveTarget.transform.Translate(transOff, Space.World); // this._scopeDitry = true; if (swipeIncrementIndex >= swipeIncrement.Length) { swipeIncrementIndex = 0; } swipeIncrement[swipeIncrementIndex] = transOff; swipeIncrementIndex++; } private void OnDestroy() { EasyTouch.On_Pinch -= EasyTouch_On_Pinch; EasyTouch.On_SwipeStart -= EasyTouch_On_SwipeStart; EasyTouch.On_Swipe -= EasyTouch_On_Swipe; EasyTouch.On_SwipeEnd -= EasyTouch_On_SwipeEnd; EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchUp2Fingers; EasyTouch.On_TouchUp2Fingers -= EasyTouch_On_TouchDown2Fingers; } /// /// 缩放 /// private void EasyTouch_On_Pinch(Gesture gesture) { if (this.lockTouch) return; this.isPinching = true; // 往外扩(放大)是负数,往内聚(缩小)是整数 float scaleDelta = gesture.deltaPinch * UnityEngine.Time.deltaTime * this.scaleSpeed; // 缩放中心点(相对于屏幕左下角) Vector2 scaleCenterPosOnScreen = gesture.position; this.Scale(scaleDelta, scaleCenterPosOnScreen); } private void EasyTouch_On_TouchDown2Fingers(Gesture gesture) { this.isPinching = true; } private void EasyTouch_On_TouchUp2Fingers(Gesture gesture) { this.isPinching = false; } /// /// 开始划 /// private void EasyTouch_On_SwipeStart(Gesture gesture) { var ray = this.mainCamera.ScreenPointToRay(gesture.position); var hits = Physics.RaycastAll(ray, this.rayMaxDistance); // 没有点击到目标 if (hits == null || hits.Length < 1) { swipeTargetValid = false; return; } for (int i = 0; i < hits.Length; i++) { if (hits[i].collider.gameObject == this.moveTarget) { this.preSwipeWorldPos = hits[i].point; break; } } swipeTargetValid = true; this.CancelLerpMove(); } /// /// 划 /// private void EasyTouch_On_Swipe(Gesture gesture) { if (this.lockTouch || this.isPinching || !this.swipeTargetValid) return; this.MoveWithScreenPos(gesture.position); } /// /// 开始划 /// private void EasyTouch_On_SwipeEnd(Gesture gesture) { if (swipeTargetValid == false) return; this.lerpMove = true; // 根据最后几帧的移动变化量来确定最后需要的持续移动强度 var transOff = new Vector3(0, 0, 0); swipeIncrementIndex = 0; for (int i = 0; i < swipeIncrement.Length; i++) { transOff += swipeIncrement[i]; swipeIncrement[i] = Vector3.zero; } transOff = this.WrapPosInRect(transOff); this.MoveTo(this.moveTarget.transform.position + transOff, true); } private void LateUpdate() { if (this.lerpMove && this.lerpMoveTarget != Vector3.zero) { var dist = Vector3.Distance(this.moveTarget.transform.position, this.lerpMoveTarget); if (dist >= 0.001f) { var curPos = Vector3.Lerp(this.moveTarget.transform.position, this.lerpMoveTarget, Time.deltaTime * this.lerpMoveSpeed); this.moveTarget.transform.position = curPos; } else { this.CancelLerpMove(); } } #if UNITY_EDITOR if (!showDebugLines) return; // 可视范围边界 var moveTargetPos = this.moveTarget.transform.position; var p0 = moveTargetPos + moveTargetLocalBounds[0]; var p1 = moveTargetPos + moveTargetLocalBounds[1]; var p2 = moveTargetPos + moveTargetLocalBounds[2]; var p3 = moveTargetPos + moveTargetLocalBounds[3]; Debug.DrawLine(p0, p1, Color.blue); // UpperLeft -> UpperRight Debug.DrawLine(p1, p2, Color.blue); // UpperRight -> LowerRight Debug.DrawLine(p2, p3, Color.blue); // LowerRight -> LowerLeft Debug.DrawLine(p3, p0, Color.blue); // LowerLeft -> UpperLeft // cam中心线 Debug.DrawLine(transform.position, transform.position + transform.forward * this.rayMaxDistance, Color.green); // cam视锥截面 Vector3[] corners = this.GetCorners(this.camDistance); Debug.DrawLine(corners[0], corners[1], Color.red); // UpperLeft -> UpperRight Debug.DrawLine(corners[1], corners[2], Color.red); // UpperRight -> LowerRight Debug.DrawLine(corners[2], corners[3], Color.red); // LowerRight -> LowerLeft Debug.DrawLine(corners[3], corners[0], Color.red); // LowerLeft -> UpperLeft // 横 Debug.DrawLine(corners[4], corners[5], Color.red); // 竖 Debug.DrawLine(corners[6], corners[7], Color.red); // 视口覆盖的区域 Debug.DrawLine(screenViewTrapezoid[0], screenViewTrapezoid[1], Color.yellow); // UpperLeft -> UpperRight Debug.DrawLine(screenViewTrapezoid[1], screenViewTrapezoid[2], Color.yellow); // UpperRight -> LowerRight Debug.DrawLine(screenViewTrapezoid[2], screenViewTrapezoid[3], Color.yellow); // LowerRight -> LowerLeft Debug.DrawLine(screenViewTrapezoid[3], screenViewTrapezoid[0], Color.yellow); // LowerLeft -> UpperLeft #endif } /// /// 对移动偏移量进行验证,超出边界要做相关处理 /// 如果超出边界是否修正为离边界最近的偏移而不是直接变为0 /// private Vector3 WrapPosInRect(Vector3 transOff, bool nearBorder = false) { var currPos = this.moveTarget.transform.position; var moveTargetPos = currPos + transOff; var st = this.CheckScreenCornerIsOutBounds(moveTargetPos); if (st == 0) return transOff; if (nearBorder == false) { var outX = (st & 1) != 0 || (st & 2) != 0 || (st & 4) != 0 || (st & 8) != 0; var outZ = (st & 16) != 0 || (st & 32) != 0 || (st & 64) != 0 || (st & 128) != 0; if (outX) transOff.x = 0; if (outZ) transOff.z = 0; } else { transOff = this.WrapTransOff(moveTargetPos, st, transOff); } return transOff; } private Vector3 WrapTransOff(Vector3 moveTargetPos, int st, Vector3 transOff){ var screenViewTrapezoid = this.GetScreenCornersPosInWorld(); var _p0 = (moveTargetPos + this.moveTargetLocalBounds[0]); var _p1 = (moveTargetPos + this.moveTargetLocalBounds[1]); var _p2 = (moveTargetPos + this.moveTargetLocalBounds[2]); var _p3 = (moveTargetPos + this.moveTargetLocalBounds[3]); // 移动后视口各个位置与边界的差,就是transOff应该再次偏移的量 // 经过偏移后使得 moveTargetPos+transOff 被限定在边界范围内 if ((st & 1) != 0) transOff.x += screenViewTrapezoid[0].x - _p0.x; else if ((st & 2) != 0) transOff.x += screenViewTrapezoid[1].x - _p1.x; else if ((st & 4) != 0) transOff.x += screenViewTrapezoid[2].x - _p2.x; else if ((st & 8) != 0) transOff.x += screenViewTrapezoid[3].x - _p3.x; if ((st & 16) != 0) transOff.z += screenViewTrapezoid[0].z - _p0.z; else if ((st & 32) != 0) transOff.z += screenViewTrapezoid[1].z - _p1.z; else if ((st & 64) != 0) transOff.z += screenViewTrapezoid[2].z - _p2.z; else if ((st & 128) != 0) transOff.z += screenViewTrapezoid[3].z - _p3.z; return transOff; } /// 检查可视区域(ScreenCorners)是否超出边界 private int CheckScreenCornerIsOutBounds(Vector3 moveTargetPos) { var screenViewTrapezoid = this.GetScreenCornersPosInWorld(); var _p0 = (moveTargetPos + moveTargetLocalBounds[0]); var _p1 = (moveTargetPos + moveTargetLocalBounds[1]); var _p2 = (moveTargetPos + moveTargetLocalBounds[2]); var _p3 = (moveTargetPos + moveTargetLocalBounds[3]); int st = 0; // 00000000,用8个位来代表4个角的x,z状态 if (_p0.x >= screenViewTrapezoid[0].x) st |= 1; if (_p1.x


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有